﻿/*
Copyright (c) 2009 Vratislav Cermak (vratacermak@gmail.com)
 
This file is part of TeachMe Project. See http://code.google.com/p/teach-me for more information.
 
TeachMe is an open source project. Using of TeachMe is free of charge.
It is distributed under GNU General Public License version 3 (GPLv3).
GPLv3 can be found at http://www.gnu.org/licenses/gpl.html.
 
This program is distributed in the hope that it will be useful, but WITHOUT
ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
FOR A PARTICULAR PURPOSE.  See the license for more details.
*/

using System;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Threading;
using System.Windows.Forms;
using TeachMe.Application.Configuration;
using TeachMe.Application.Data;
using TeachMe.Application.Events;
using TeachMe.Application.Exceptions;
using TeachMe.Application.Forms;
using TeachMe.Application.Properties;
using TeachMe.Application.Themes;
using TeachMe.Application.Win32;
using Timer = System.Windows.Forms.Timer;
using Microsoft.Win32;

namespace TeachMe.Application
{
    public class Root : IDisposable
    {
        private class DictionaryEntriesPriorityInfo
        {
            private readonly Dictionary<Priority, int> _priorityToCount;

            public Dictionary Dictionary { get; private set; }

            public DictionaryEntriesPriorityInfo(Dictionary dictionary)
            {
                _priorityToCount = new Dictionary<Priority, int>();

                Dictionary = dictionary;

                foreach (Priority priority in dictionary.Priorities.Items.Values)
                {
                    Raise(priority);
                }

                if (dictionary.Entries.Count - dictionary.Priorities.Count > 0)
                {
                    Raise(Priority.Normal, dictionary.Entries.Count - dictionary.Priorities.Count);
                }
            }

            public int GetCount(Priority priority)
            {
                return _priorityToCount.ContainsKey(priority) ? _priorityToCount[priority] : 0;
            }

            private void Raise(Priority priority, int number)
            {
                if (!_priorityToCount.ContainsKey(priority))
                {
                    _priorityToCount.Add(priority, number);
                }
                else
                {
                    _priorityToCount[priority] += number;
                }
            }

            private void Raise(Priority priority)
            {
                Raise(priority, 1);
            }
        }

        public event EventHandler<DictionaryEntriesEventArgs> DictionaryEntriesModified;
        public event EventHandler<DictionaryEventArgs> DictionaryUserSettingsModified;
        public event EventHandler<DictionaryEventArgs> DictionaryRemoved;
        public event EventHandler<BaloonTooltipRequestEventArgs> BallonRequested;

        private readonly UserSettings _userSettings;
        private readonly MachineSettings _machineSettings;
        private readonly ImportExcludes _importExcludes;
        private readonly List<Dictionary> _dictionaries;
        private readonly DisplayManager _displayManager;
        private readonly Random _random = new Random(DateTime.Now.Millisecond);
        private Timer _timer;
        private Timer _suspendTimer;
        private Timer _newVersionCheckTimer;
        private bool _turboMode;
        private readonly PrioritiesStore _prioritiesStore;
        private bool _ignoreEntryRemovedEvent;
        private readonly FormsManager _formsManager;
        private readonly ContextMenusManager _contextMenusManager;
        private readonly PathsProvider _pathsProvider;
        private readonly HotkeysManager _hotkeysManager;
        private bool _disposed;
        private int _suspendCountdown;
        private NotifyIcon _notifyIcon;
        private readonly bool _justInstalled;
        private readonly TemporaryContentManager _temporaryContentManager;
        private readonly string[] _supportedCultures = new[] { "en-GB", "cs-CZ" };
        private readonly Queue<Entry> _history = new Queue<Entry>();
        private readonly SynchronizationContext _synchronizationContext;

        public string[] SupportedCultures
        {
            get
            {
                return _supportedCultures;
            }
        }

        public Root()
        {
            _pathsProvider = new PathsProvider();
            _prioritiesStore = new PrioritiesStore(_pathsProvider.Priorities);
            _machineSettings = MachineSettings.Load(_pathsProvider.MachineSettings, out _justInstalled);
            _userSettings = UserSettings.Load(_pathsProvider.UserSettings);
            _importExcludes = ImportExcludes.Load(_pathsProvider.ImportExcludes);
            _dictionaries = new List<Dictionary>();
            _hotkeysManager = new HotkeysManager(_userSettings);
            _contextMenusManager = new ContextMenusManager(this);
            _formsManager = new FormsManager(this);
            _temporaryContentManager = new TemporaryContentManager();
            _displayManager = new DisplayManager(this);
            _synchronizationContext = SynchronizationContext.Current;

            SetLanguage();
            LoadDictionaries();
            UseTheme(_userSettings.CurrentThemePath);
            UpdateNewEntryIntervalTimer();
            UpdateNewVersionCheckIntervalTimer();
            DisplayRequiredEntries();
            
            _userSettings.NewVersionCheckInterval.Changed += delegate { UpdateNewVersionCheckIntervalTimer(); };
            _userSettings.Language.Changed += delegate { SetLanguage(); };
            _userSettings.NewEntryInterval.Changed += delegate { UpdateNewEntryIntervalTimer(); };
            _userSettings.TurboModeInterval.Changed += delegate { UpdateNewEntryIntervalTimer(); };
            _userSettings.TransientCurrentThemePath.Changed += delegate { UseTheme(_userSettings.CurrentThemePath); };
            _userSettings.MaxEntriesNumber.Changed += delegate { DisplayRequiredEntries(); };
            _userSettings.NewEntryMode.Changed += OnNewEntryModeChanged;

            _displayManager.EntryClosed += OnEntryClosed;
        }

        void OnNewEntryModeChanged(object sender, EventArgs e)
        {
            UpdateNewEntryIntervalTimer();
            DisplayRequiredEntries();
        }

        private void UpdateNewVersionCheckIntervalTimer()
        {
            if (_newVersionCheckTimer != null)
            {
                _newVersionCheckTimer.Enabled = false;
                _newVersionCheckTimer.Tick -= OnNewVersionCheckInterval;
            }

            _newVersionCheckTimer = new Timer();
            _newVersionCheckTimer.Tick += OnNewVersionCheckInterval;
            _newVersionCheckTimer.Interval = 60 * 60 * 1000; // every hour
            
            _newVersionCheckTimer.Enabled = _userSettings.NewVersionCheckInterval.Value != Interval.Never;
        }

        void OnNewVersionCheckInterval(object sender, EventArgs e)
        {
            if (_formsManager.Get<NewVersionForm>() != null)
            {
                Logging.Debug(GetType(), "New version dialog is already displayed.");
            }

            Logging.Debug(GetType(), "New version check interval occured. Last check performed on {0}.", _machineSettings.LastNewVersionCheck);

            bool check;

            switch (_userSettings.NewVersionCheckInterval.Value)
            {
                case Interval.Daily:
                    check = (_machineSettings.LastNewVersionCheck.AddDays(1) < DateTime.Now);
                    break;
                case Interval.Weekly:
                    check = (_machineSettings.LastNewVersionCheck.AddDays(7) < DateTime.Now);
                    break;
                default:
                    throw new ArgumentOutOfRangeException();
            }

            if (check)
            {
                Logging.Debug(GetType(), "Will check for new version.");

                using (_machineSettings.Edit())
                {
                    _machineSettings.LastNewVersionCheck = DateTime.Now;
                }

                OnlineVersionChecker.Check(this, true);
            }
        }

        private void SetLanguage()
        {
            CultureInfo cultureInfo = GetLanguage();

            Logging.Debug(GetType(), "Language will be changed to '{0}'.", cultureInfo);

            Thread.CurrentThread.CurrentUICulture = cultureInfo;
        }

        public CultureInfo GetLanguage()
        {
            string value = _userSettings.Language.Value;

            if (String.IsNullOrEmpty(value))
            {
                foreach (string supportedCulture in SupportedCultures)
                {
                    if (String.Compare(CultureInfo.CurrentCulture.Name, supportedCulture) == 0)
                    {
                        return CultureInfo.CurrentCulture;
                    }
                }

                return CultureInfo.GetCultureInfo(SupportedCultures[0]);
            }

            try
            {
                return CultureInfo.GetCultureInfo(value);
            }
            catch (Exception)
            {
                Logging.Error(GetType(), "Cannot get culture info '{0}' specified in user settings.", value);

                return CultureInfo.GetCultureInfo(SupportedCultures[0]);
            }
        }

        public DisplayManager DisplayManager
        {
            get { return _displayManager; }
        }

        public void OnEntryClosed(object sender, EventArgs e)
        {
            if (_ignoreEntryRemovedEvent)
            {
                return;
            }

            DisplayRequiredEntries();
        }

        private void DisplayRequiredEntries()
        {
            if (Suspended)
            {
                return;
            }

            Logging.Debug(GetType(), "Displaying required entries.");

            if (_displayManager.EntriesCount > _userSettings.MaxEntriesNumber.Value)
            {
                _displayManager.SetNumberOfDisplayedEntries(_userSettings.MaxEntriesNumber.Value);
            }
            else
            {
                if (_userSettings.NewEntryMode.Value == NewEntryMode.Persistent)
                {
                    List<NewEntryDescriptorData> newEntries = new List<NewEntryDescriptorData>();

                    for (int idx = _displayManager.EntriesCount; idx < _userSettings.MaxEntriesNumber.Value; idx++)
                    {
                        Dictionary entryDictionary;
                        EntryTextDirection entryTextDirection;
                        Entry entry = GetRandomEntry(out entryDictionary, out entryTextDirection);

                        if (entry == null)
                        {
                            continue;
                        }

                        newEntries.Add(new NewEntryDescriptorData(entryDictionary, entry, GetEntryTextDirection(entryTextDirection)));
                    }

                    if (newEntries.Count > 0)
                    {
                        _displayManager.AddEntries(newEntries);
                        
                        // when new entry is displayed it may be on user's request - reset timer so new entry will last long enough
                        _timer.Stop();
                        _timer.Start();
                    }
                }
            }
        }

        public void UpdateNewEntryIntervalTimer()
        {
            if (_timer != null)
            {
                _timer.Enabled = false;
                _timer.Tick -= OnNewEntryIntervalTimerElapsed;
            }

            _timer = new Timer();

            if (_turboMode)
            {
                _timer.Interval = _userSettings.TurboModeInterval.Value * 1000;
            }
            else
            {
                _timer.Interval = (_userSettings.NewEntryInterval.Value * 60) * 1000;
            }

            _timer.Tick += OnNewEntryIntervalTimerElapsed;
            _timer.Enabled = _userSettings.NewEntryMode.Value != NewEntryMode.OnDemand;

            Logging.Debug(GetType(), "New entry interval timer is now '{0}'.", _timer.Enabled ? "Enabled" : "Disabled");
        }

        public bool TurboModeEnabled
        {
            get
            {
                return UserSettings.NewEntryMode.Value != NewEntryMode.OnDemand && !Suspended;
            }
        }

        public bool TurboMode
        {
            get
            {
                return _turboMode;
            }
            set
            {
                _turboMode = value;
                
                UpdateNewEntryIntervalTimer();
            }
        }

        private void OnNewEntryIntervalTimerElapsed(object sender, EventArgs e)
        {
            if (Suspended)
            {
                return;
            }

            if (IsScreenSaverActive())
            {
                Logging.Debug(GetType(), "Screensaver is active, interval entry displaying is canceled.");

                return;
            }

            Logging.Debug(GetType(), "New entry interval has elapsed.");

            DisplayEntry();
        }

        public static bool IsScreenSaverActive()
        {
            bool isActive = false;
            if (!NativeMethods.SystemParametersInfo(Constants.SPI_GETSCREENSAVERRUNNING, 0, ref isActive, (int)SPIF.SPIF_SENDCHANGE))
            {
                throw new InvalidOperationException();
            }
            return isActive;
        }


        public void DisplayEntry(Dictionary dictionary, int entryId)
        {
            if (_userSettings.MaxEntriesNumber.Value > 0)
            {
                if (_displayManager.EntriesCount == _userSettings.MaxEntriesNumber.Value)
                {
                    try
                    {
                        _ignoreEntryRemovedEvent = true;

                        _displayManager.RemoveFirstEntry();
                    }
                    finally
                    {
                        _ignoreEntryRemovedEvent = false;
                    }
                }
                
                Entry entry = dictionary.Entries.Items[entryId];
                EntryTextDirection entryTextDirection = dictionary.UserData.EntryTextDirection;
                if (entry != null)
                {
                    _displayManager.AddEntry(new NewEntryDescriptorData(dictionary, entry, GetEntryTextDirection(entryTextDirection)));
                }
            }
        }

        public void DisplayEntry()
        {
            if (_userSettings.MaxEntriesNumber.Value > 0)
            {
                if (_displayManager.EntriesCount == _userSettings.MaxEntriesNumber.Value)
                {
                    try
                    {
                        _ignoreEntryRemovedEvent = true;

                        _displayManager.RemoveFirstEntry();
                    }
                    finally
                    {
                        _ignoreEntryRemovedEvent = false;
                    }
                }

                Dictionary entryDictionary;
                EntryTextDirection entryTextDirection;
                Entry entry = GetRandomEntry(out entryDictionary, out entryTextDirection);
                if (entry != null)
                {
                    _displayManager.AddEntry(new NewEntryDescriptorData(entryDictionary, entry, GetEntryTextDirection(entryTextDirection)));
                }
            }
        }

        private RealEntryTextDirection GetEntryTextDirection(EntryTextDirection entryTextDirection)
        {
            switch (entryTextDirection)
            {
                case EntryTextDirection.FromTo:
                    return RealEntryTextDirection.FromTo;
                case EntryTextDirection.ToFrom:
                    return RealEntryTextDirection.ToFrom;
                case EntryTextDirection.Both:
                    switch (_random.Next(2))
                    {
                        case 0:
                            return RealEntryTextDirection.FromTo;
                        case 1:
                            return RealEntryTextDirection.ToFrom;
                        default:
                            throw new ArgumentOutOfRangeException();
                    }
                default:
                    throw new ArgumentOutOfRangeException();
            }
        }

        private int GenEnabledDictsCount()
        {
            int result = 0;

            foreach (Dictionary dictionary in _dictionaries)
            {
                if (dictionary.UserData.Enabled)
                {
                    result++;
                }
            }

            return result;
        }

        private Entry GetRandomEntry(out Dictionary entryDictionary, out EntryTextDirection entryTextDirection)
        {
            entryDictionary = null;
            entryTextDirection = EntryTextDirection.Unknown;

            if (GenEnabledDictsCount() == 0)
            {
                return null;
            }

            // let's toss for dictionaries we will used based on their priority
            
            List<Dictionary> dictionaries = GetRandomDictionaries();

            // collecting stats about entries

            List<DictionaryEntriesPriorityInfo> entriesPriorityInfoList = new List<DictionaryEntriesPriorityInfo>();

            foreach (Dictionary dictionary in dictionaries)
            {
                if (dictionary.UserData.Enabled)
                {
                    entriesPriorityInfoList.Add(new DictionaryEntriesPriorityInfo(dictionary));
                }
            }

            // get random entry; this entry should not be used in last 10 random choices but we will try this just 5 times in case
            // we do not have enough entries or we are out of luck :)

            Entry randomEntry = null;

            for (int attempt = 0; attempt < 5; attempt++)
            {
                randomEntry = GetRandomEntry(entriesPriorityInfoList, out entryDictionary, out entryTextDirection);

                if (randomEntry == null)
                {
                    Logging.Warn(GetType(), "Random entry could not be get.");
                    break;
                }
                
                if (!_history.Contains(randomEntry))
                {
                    if (_history.Count >= 10)
                    {
                        _history.Dequeue();
                    }
                    
                    _history.Enqueue(randomEntry);

                    break;
                }
            }

            return randomEntry;
        }

        private List<Dictionary> GetRandomDictionaries()
        {
            if (_dictionaries == null)
            {
                throw new InvalidOperationException();
            }

            List<Dictionary> dictionaries = new List<Dictionary>();
            int scale = 0;

            while (dictionaries.Count == 0)
            {
                foreach (Dictionary dictionary in _dictionaries)
                {
                    if (!dictionary.UserData.Enabled)
                    {
                        continue;
                    }

                    int randomNumber = _random.Next(100);

                    switch (dictionary.UserData.Priority)
                    {
                        case Priority.VeryLow:
                            if (randomNumber < 25 + scale)
                            {
                                dictionaries.Add(dictionary);
                            }
                            break;
                        case Priority.Low:
                            if (randomNumber < 50 + scale)
                            {
                                dictionaries.Add(dictionary);
                            }
                            break;
                        case Priority.Normal:
                            if (randomNumber < 75 + scale)
                            {
                                dictionaries.Add(dictionary);
                            }
                            break;
                        case Priority.High:
                            dictionaries.Add(dictionary);
                            break;
                        default:
                            throw new ArgumentOutOfRangeException();
                    }
                }

                scale += 25;
            }

            Logging.Debug(GetType(), "We have {0} random dictionaries.", dictionaries.Count);

            return dictionaries;
        }

        private Entry GetRandomEntry(IEnumerable<DictionaryEntriesPriorityInfo> entriesPriorityInfoList, out Dictionary entryDictionary, out EntryTextDirection entryTextDirection)
        {
            entryTextDirection = EntryTextDirection.Unknown;
            entryDictionary = null;

            int veryLowPriorityCount = 0;
            int lowPriorityCount = 0;
            int normalPriorityCount = 0;
            int highPriorityCount = 0;

            foreach (DictionaryEntriesPriorityInfo entriesPriorityInfo in entriesPriorityInfoList)
            {
                veryLowPriorityCount += entriesPriorityInfo.GetCount(Priority.VeryLow);
                lowPriorityCount += entriesPriorityInfo.GetCount(Priority.Low);
                normalPriorityCount += entriesPriorityInfo.GetCount(Priority.Normal);
                highPriorityCount += entriesPriorityInfo.GetCount(Priority.High);
            }

            int veryLowPriorityRate = veryLowPriorityCount * (_userSettings.VeryLowPriorityRate.Value);
            int lowPriorityRate = lowPriorityCount * (_userSettings.LowPriorityRate.Value);
            int normalPriorityRate = normalPriorityCount * (_userSettings.NormalPriorityRate.Value);
            int highPriorityRate = highPriorityCount * (_userSettings.HighPriorityRate.Value);

            Priority randomPriority;

            int randomRate = _random.Next(veryLowPriorityRate + lowPriorityRate + normalPriorityRate + highPriorityRate);
            int entriesInRandomPriority;

            if (randomRate < veryLowPriorityRate)
            {
                randomPriority = Priority.VeryLow;
                entriesInRandomPriority = veryLowPriorityCount;
            }
            else if (randomRate < veryLowPriorityRate + lowPriorityRate)
            {
                randomPriority = Priority.Low;
                entriesInRandomPriority = lowPriorityCount;
            }
            else if (randomRate < veryLowPriorityRate + lowPriorityRate + normalPriorityRate)
            {
                randomPriority = Priority.Normal;
                entriesInRandomPriority = normalPriorityCount;
            }
            else if (randomRate < veryLowPriorityRate + lowPriorityRate + normalPriorityRate + highPriorityRate)
            {
                randomPriority = Priority.High;
                entriesInRandomPriority = highPriorityCount;
            }
            else
            {
                return null;
            }

            int randomEntryIdx = _random.Next(entriesInRandomPriority);

            foreach (DictionaryEntriesPriorityInfo entriesPriorityInfo in entriesPriorityInfoList)
            {
                if (randomEntryIdx < entriesPriorityInfo.GetCount(randomPriority))
                {
                    foreach (Entry entry in entriesPriorityInfo.Dictionary.Entries.Items.Values)
                    {
                        if (entriesPriorityInfo.Dictionary.GetEntryPriority(entry.Id) == randomPriority)
                        {
                            if (randomEntryIdx == 0)
                            {
                                entryDictionary = entriesPriorityInfo.Dictionary;
                                entryTextDirection = entriesPriorityInfo.Dictionary.UserData.EntryTextDirection;

                                return entry;
                            }

                            randomEntryIdx--;
                        }
                    }

                    break;
                }

                randomEntryIdx -= entriesPriorityInfo.GetCount(randomPriority);
            }

            throw new InvalidOperationException();
        }

        private void LoadDictionaries()
        {
            if (_userSettings.DictionariesUserData != null)
            {
                List<DictionaryUserData> toRemove = new List<DictionaryUserData>();

                foreach (DictionaryUserData descriptor in _userSettings.DictionariesUserData)
                {
                    Logging.Debug(GetType(), "Loading dictionary '{0}'.", descriptor.Path);

                    try
                    {
                        Dictionary dictionary = new Dictionary(descriptor, _prioritiesStore);

                        dictionary.EntriesModified += OnDictionaryEntriesModified;
                        dictionary.UserSettingsModified += OnDictionaryUserSettingsModified;

                        _dictionaries.Add(dictionary);
                    }
                    catch (DictionaryException e)
                    {
                        Logging.Error(GetType(), e, "Dictionary could not be loaded.");

                        if (MessageBox.Show(String.Format(Resources.DictionaryLoadError, descriptor.Path), Resources.MessageBoxCaption, MessageBoxButtons.YesNo, MessageBoxIcon.Error) == DialogResult.Yes)
                        {
                            toRemove.Add(descriptor);
                        }
                    }
                }

                if (toRemove.Count > 0)
                {
                    using (_userSettings.Edit())
                    {
                        foreach (DictionaryUserData dictionaryUserData in toRemove)
                        {
                            Logging.Debug(GetType(), "Removing dictionary '{0}'.", dictionaryUserData.Path);

                            _userSettings.DictionariesUserData.Remove(dictionaryUserData);
                        }
                    }
                }
            }
        }

        void OnDictionaryEntriesModified(object sender, DictionaryEntriesEventArgs eventArgs)
        {
            FireDictionaryEntriesModified(eventArgs);

            DisplayRequiredEntries();
        }

        void OnDictionaryUserSettingsModified(object sender, DictionaryEventArgs e)
        {
            FireDictionaryUserSettingsModified(e);

            DisplayRequiredEntries();
        }

        private void FireDictionaryEntriesModified(DictionaryEntriesEventArgs eventArgs)
        {
            EventHandler<DictionaryEntriesEventArgs> handler = DictionaryEntriesModified;
            if (handler != null)
            {
                handler(this, eventArgs);
            }
        }

        private void FireDictionaryUserSettingsModified(DictionaryEventArgs eventArgs)
        {
            EventHandler<DictionaryEventArgs> handler = DictionaryUserSettingsModified;
            if (handler != null)
            {
                handler(this, eventArgs);
            }
        }

        private void FireDictionaryRemoved(DictionaryEventArgs eventArgs)
        {
            EventHandler<DictionaryEventArgs> handler = DictionaryRemoved;
            if (handler != null)
            {
                handler(this, eventArgs);
            }
        }

        public Dictionary CreateNewDictionary(string path, string name, string description, string from, string to)
        {
            DictionaryUserData dictionaryUserData = new DictionaryUserData {Path = path, Enabled = true, EntryTextDirection = EntryTextDirection.FromTo};

            _userSettings.DictionariesUserData.Add(dictionaryUserData);

            NewDictionaryData newDictionaryData = new NewDictionaryData();
            newDictionaryData.UserData = dictionaryUserData;
            newDictionaryData.PrioritiesStore = _prioritiesStore;
            newDictionaryData.Name = name;
            newDictionaryData.From = from;
            newDictionaryData.To = to;
            newDictionaryData.Description = description;

            Dictionary dictionary = Dictionary.Create(newDictionaryData);

            dictionary.EntriesModified += OnDictionaryEntriesModified;
            dictionary.UserSettingsModified += OnDictionaryUserSettingsModified;

            _dictionaries.Add(dictionary);

            return dictionary;
        }

        public Dictionary AddNewDictionary(string path)
        {
            DictionaryUserData descriptor = new DictionaryUserData();
            descriptor.Path = path;
            descriptor.Enabled = true;
            descriptor.EntryTextDirection = EntryTextDirection.FromTo;
            descriptor.Priority = Priority.Normal;

            _userSettings.DictionariesUserData.Add(descriptor);

            Dictionary dictionary = new Dictionary(descriptor, _prioritiesStore);

            dictionary.EntriesModified += OnDictionaryEntriesModified;
            dictionary.UserSettingsModified += OnDictionaryUserSettingsModified;

            _dictionaries.Add(dictionary);

            DisplayRequiredEntries();

            return dictionary;
        }

        public void RemoveDictionary(Dictionary dictionary)
        {
            dictionary.EntriesModified -= OnDictionaryEntriesModified;
            dictionary.UserSettingsModified -= OnDictionaryUserSettingsModified;

            _userSettings.DictionariesUserData.Remove(dictionary.UserData);
            _dictionaries.Remove(dictionary);

            FireDictionaryRemoved(new DictionaryEventArgs { Dictionary = dictionary });
        }

        public UserSettings UserSettings
        {
            get { return _userSettings; }
        }

        public List<Dictionary> Dictionaries
        {
            get { return _dictionaries; }
        }

        public FormsManager FormsManager
        {
            get { return _formsManager; }
        }

        public ContextMenusManager ContextMenusManager
        {
            get { return _contextMenusManager; }
        }

        private Theme LoadTheme(string path)
        {
            Theme theme = null;

            if (!String.IsNullOrEmpty(path))
            {
                if (File.Exists(path))
                {
                    Logging.Debug(GetType(), "Loading theme '{0}'.", path);

                    try
                    {
                        theme = ThemeLoader.LoadFromArchive(_temporaryContentManager, path);
                    }
                    catch (ThemeException e)
                    {
                        Logging.Error(GetType(), e, "Theme '{0}' could not be loaded.", path);
                    }
                }
            }

            if (theme == null)
            {
                try
                {
                    Logging.Debug(GetType(), "Loading default theme.");

                    Assembly executingAssembly = Assembly.GetExecutingAssembly();

                    using (Stream stream = executingAssembly.GetManifestResourceStream("TeachMe.Application.Embedded.DefaultTheme.tme"))
                    {
                        theme = ThemeLoader.LoadFromStream(_temporaryContentManager, stream);
                    }
                }
                catch (Exception e)
                {
                    Logging.Error(GetType(), e, "Default theme could not be loaded.");

                    throw;
                }
            }

            return theme;
        }

        public void UseDefaultTheme()
        {
            _displayManager.SetTheme(LoadTheme(null));
        }

        public void UseTheme(Theme theme)
        {
            _displayManager.SetTheme(theme);
        }

        public void UseTheme(string path)
        {
            _displayManager.SetTheme(LoadTheme(path));
        }

        public Theme CurrentTheme
        {
            get { return LoadTheme(_userSettings.CurrentThemePath); }
        }

        public MachineSettings MachineSettings
        {
            get { return _machineSettings; }
        }

        public HotkeysManager HotkeysManager
        {
            get { return _hotkeysManager; }
        }

        public void RefreshDictionary(Dictionary dictionary)
        {
            dictionary.Load();
        }

        public void CreateEntry()
        {
            _formsManager.ShowCreateEntryDialog();
        }

        public void HandleHotkey(ushort id)
        {
            switch (_hotkeysManager.GetHotkeyType(id))
            {
                case HotkeyType.CreateEntry:
                    CreateEntry();
                    break;
                case HotkeyType.DisplayEntry:
                    DisplayEntry();
                    break;
                case HotkeyType.Suspend:
                    if (Suspended)
                    {
                        Resume();
                    }
                    else
                    {
                        Suspend(UserSettings.HotkeySuspendInterval.Value);
                    }
                    break;
                case HotkeyType.TurboMode:
                    if (TurboModeEnabled)
                    {
                        TurboMode = !TurboMode;
                    }
                    break;
                default:
                    throw new ArgumentOutOfRangeException();
            }
        }

        protected virtual void Dispose(bool disposing)
        {
            if (!_disposed)
            {
                if (disposing)
                {
                    _hotkeysManager.UnregisterAll();
                    
                    using (_machineSettings.Edit())
                    {
                        // "touch" to ensure that machine settings were created
                    }
                }

                // unmanaged resources

                _disposed = true;
            }
        }

        public void Dispose()
        {
            Dispose(true);
        }

        public void Resume()
        {
            Logging.Debug(GetType(), "Resuming.");

            _suspendTimer.Enabled = false;
            _suspendTimer.Tick -= OnSuspendTimerTick;

            _notifyIcon.Icon = Resources.AppIcon;

            //

            Suspended = false;

            DisplayRequiredEntries();
        }

        public void Suspend(int minutes)
        {
            Logging.Debug(GetType(), "Suspending for '{0}' minutes.", minutes);

            Suspended = true;

            //

            _displayManager.CloseEntries();

            _suspendTimer = new Timer();
            _suspendTimer.Tick += OnSuspendTimerTick;
            _suspendTimer.Interval = 1000 * 60; // once per minute
            _suspendTimer.Enabled = true;

            _suspendCountdown = minutes;

            // _notifyIcon.Icon = Resources.AppIcondDisabled;

            // i think this was more annoying than useful
            // ShowBaloon(Resources.MessageBoxCaption, String.Format(Resources.JustSuspendedBallonText, minutes));
        }

        public void SetNotifyIcon(NotifyIcon notifyIcon)
        {
            _notifyIcon = notifyIcon;
        }

        void OnSuspendTimerTick(object sender, EventArgs e)
        {
            _suspendCountdown--;

            if (_suspendCountdown <= 0)
            {
                Resume();
            }
        }

        public bool Suspended { get; private set; }

        public string GetNotifyIconText()
        {
            if (Suspended)
            {
                return String.Format(Resources.NotifyIconSuspendedText, _suspendCountdown);
            }

            return Resources.NotifyIconText;
        }

        public bool AutoStart
        {
            get
            {
                try
                {
                    using (RegistryKey registryKey = Registry.CurrentUser.OpenSubKey("SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Run", true))
                    {
                        if (registryKey != null)
                        {
                            object value = registryKey.GetValue("teachMe");
                            if (value is string)
                            {
                                return Utilities.Equals(((string)value).Trim('\"'), System.Windows.Forms.Application.ExecutablePath);
                            }
                        }
                    }
                }
                catch (Exception exception)
                {
                    Logging.Error(GetType(), exception, "Could not read registry to determine if teachMe is started automatically.");
                }

                return false;
            }
            set
            {
                Logging.Debug(GetType(), "Updating auto start value to '{0}'", value);

                try
                {
                    using (RegistryKey registryKey = Registry.CurrentUser.OpenSubKey("SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Run", true))
                    {
                        if (value)
                        {
                            registryKey.SetValue("teachMe", String.Format("\"{0}\"",System.Windows.Forms.Application.ExecutablePath));
                        }
                        else 
                        {
                            if (registryKey.GetValue("teachMe") != null)
                            {
                                registryKey.DeleteValue("teachMe");
                            }
                        }
                    }
                }
                catch (Exception exception)
                {
                    Logging.Error(GetType(), exception, "Could not update registry to ensure that teachMe is started automatically or not.");

                    MessageBox.Show(value ? Resources.ErrorEnablingAutoRun : Resources.ErrorDisablingAutoRun, Resources.MessageBoxCaption, MessageBoxButtons.OK, MessageBoxIcon.Error);
                }
            }
        }

        public bool JustInstalled
        {
            get { return _justInstalled; }
        }

        public TemporaryContentManager TemporaryContentManager
        {
            get { return _temporaryContentManager; }
        }

        public ImportExcludes ImportExcludes
        {
            get { return _importExcludes; }
        }

        public void ShowBaloon(string title, string text)
        {
            EventHandler<BaloonTooltipRequestEventArgs> handler = BallonRequested;   
            if (handler != null)
            {
                handler(this, new BaloonTooltipRequestEventArgs {Title = title, Text = text});
            }
        }

        public string CurrentVersion
        {
            get
            {
                Version version = Assembly.GetExecutingAssembly().GetName().Version;

                return String.Format("{0}.{1}.{2}.{3}", version.Major, version.Minor, version.Build, version.Revision);
            }
        }

        private void DisplayOnlineVersionInfo(object state)
        {
            OnlineVersion onlineVersion = (OnlineVersion) state;
            if (onlineVersion == null)
            {
                throw new InvalidOperationException();
            }

            NewVersionForm form = new NewVersionForm(this);
            form.Update(onlineVersion);

            _formsManager.Add(form);

            form.Closed += delegate
            {
                _formsManager.Remove(form);
            };

            form.Show();
        }

        public void DisplayOnlineVersionInfo(OnlineVersion onlineVersion, bool silent)
        {
            if (onlineVersion.IsEmpty)
            {
                if (!silent)
                {
                    MessageBox.Show(Resources.OnlineCheckFailed, Resources.MessageBoxCaption, MessageBoxButtons.OK, MessageBoxIcon.Warning);
                }
                return;
            }

            if (Utilities.CompareVersions(CurrentVersion, onlineVersion.Value) < 0)
            {
                _synchronizationContext.Post(DisplayOnlineVersionInfo, onlineVersion);
            }
            else
            {
                if (!silent)
                {
                    MessageBox.Show(Resources.NoNewVersion, Resources.MessageBoxCaption, MessageBoxButtons.OK, MessageBoxIcon.Information);
                }
            }
        }

        internal void AddDefaultDictionaries()
        {
            string[] defaultDicts;

            try
            {
                defaultDicts = Directory.GetFiles(_pathsProvider.DefaultDictsFolder, "*.xml");
            }
            catch (DirectoryNotFoundException exception)
            {
                Logging.Error(GetType(), exception, "Directory with default dictionaries does not exists.");

                return;
            }

            using (_userSettings.Edit())
            {
                foreach (string path in defaultDicts)
                {
                    bool alreadyUsed = false;

                    foreach (Dictionary dict in _dictionaries)
                    {
                        if (String.Compare(dict.UserData.Path, path, true) == 0)
                        {
                            alreadyUsed = true;
                        }
                    }

                    if (!alreadyUsed)
                    {
                        try
                        {
                            AddNewDictionary(path);
                        }
                        catch (DictionaryException exception)
                        {
                            Logging.Error(GetType(), exception, "Default dictionary could not be added.");
                        }
                    }
                }
            }
        }

        public int EnabledDictionariesCount
        {
            get
            {
                return _dictionaries.Count(dictionary => dictionary.UserData.Enabled);
            }
        }
    }
}
